package ch.areion.common.message;

import java.sql.Timestamp;
import java.util.Calendar;
import java.util.StringTokenizer;

/**
 * Message class wich implements the areion tcp socket protocoll.
 * 
 * @author leupibr
 * @version 1.1
 */
public class Message {
	/**
	 * The field separator of the areion tcp socket protocoll.
	 */
	private static final String SEPARATOR = ".";

	/**
	 * Number of generated messages.
	 */
	public static long Count = 0;

	/**
	 * The raw message.
	 */
	private String msg = "";

	/**
	 * The timestamp.
	 */
	private long ts;
	/**
	 * The version of the message.
	 */
	private Version v = Version.UNDEFINED;
	/**
	 * The type of the message (ie. Error)
	 */
	private Type t;
	/**
	 * The module of the message.
	 */
	private Module m;
	/**
	 * The data to be sent.
	 */
	private String data;

	/**
	 * Flag if message is packed.
	 */
	private boolean packed;
	/**
	 * Flag if message is unpacked
	 */
	private boolean unpacked;

	/**
	 * Default Constructor (private) increments message counter
	 */
	private Message() {
		Message.Count++;
	}

	/**
	 * Constructor for received message
	 * 
	 * @param msg A raw string with the message
	 */
	public Message(String msg) {
		this();
		this.msg = msg;
		this.packed = true;
		this.unpacked = false;
	}

	/**
	 * Constructor for new message
	 * 
	 * @param v Version of the message
	 * @param t Type of the message
	 * @param m Involved module
	 * @param data Raw data
	 */
	public Message(Version v, Type t, Module m, String data) {
		this();
		this.ts = Calendar.getInstance().getTime().getTime();
		this.v = v;
		this.t = t;
		this.m = m;
		this.data = data;
		this.packed = false;
		this.unpacked = true;
	}

	/**
	 * Unpacks if needed, and returns the version.
	 * 
	 * @return The version of the message.
	 */
	public Version getVersion() {
		if (!unpacked)
			unpack();
		return this.v;
	}

	/**
	 * Unpacks if needed and returns the timestamp.
	 * 
	 * @return The timestamp of the message.
	 */
	public Timestamp getTimestamp() {
		if (!unpacked)
			unpack();
		return new Timestamp(ts);
	}

	/**
	 * Unpacks if needed and returns the type.
	 * 
	 * @return The type of the message
	 */
	public Type getType() {
		if (!unpacked)
			unpack();
		return this.t;
	}

	/**
	 * Unpacks if needed and returns the module.
	 * 
	 * @return The module of the message.
	 */
	public Module getModule() {
		if (!unpacked)
			unpack();
		return this.m;
	}

	/**
	 * Unpacks if needed and returns the data field.
	 * 
	 * @return The data of the message.
	 */
	public String getData() {
		if (!unpacked)
			unpack();
		return this.data;
	}

	/**
	 * Unpacks the message.
	 * 
	 * @return The raw message
	 */
	private String pack() {
		this.packed = true;
		return v.getValue() + SEPARATOR + ts + SEPARATOR + t.getValue() + SEPARATOR
		    + m.getValue() + SEPARATOR + data;
	}

	/**
	 * Unpacks the message.
	 * Creates default messsage on error with the raw message as data.
	 */
	private void unpack() {
		final StringTokenizer tok = new StringTokenizer(this.msg, SEPARATOR);

		try {
			this.v = getMsgVersion(tok.nextToken());
			this.ts = Long.parseLong(tok.nextToken());
			this.t = getMsgType(tok.nextToken());
			this.m = getMsgModule(tok.nextToken());
			this.data = tok.nextToken();
			while (tok.hasMoreElements())
				this.data += SEPARATOR + tok.nextToken();

		} catch (Exception ex) {
			this.v = Version.UNDEFINED;
			this.ts = Calendar.getInstance().getTime().getTime();
			this.t = Type.ERROR;
			this.m = Module.OTHER;
			this.data = "Unable to unpack: \"" + this.msg + "\"";
		}
		this.unpacked = true;
	}

	private Type getMsgType(String s) {
		int t = Integer.parseInt(s);
		if (Type.ERROR.getValue() == t)
			return Type.ERROR;
		else if (Type.WARNING.getValue() == t)
			return Type.WARNING;

		return Type.INFO;
	}

	private Version getMsgVersion(String s) {
		int v = Integer.parseInt(s);
		if (Version.V01.getValue() == v)
			return Version.V01;
		return Version.UNDEFINED;
	}

	private Module getMsgModule(String s) {
		int m = Integer.parseInt(s);
		if (Module.CORE.getValue() == m)
			return Module.CORE;
		else if (Module.ENGINE.getValue() == m)
			return Module.ENGINE;
		else if (Module.SENSOR.getValue() == m)
			return Module.SENSOR;
		else if (Module.IO.getValue() == m)
			return Module.IO;
		else if (Module.PERIPHERY.getValue() == m)
			return Module.PERIPHERY;
		else if (Module.COMMUNICATION.getValue() == m)
			return Module.COMMUNICATION;

		return Module.OTHER;
	}

	@Override
	public String toString() {
		if (!packed)
			msg = pack();
		return msg;
	}

	public String format() {
		String str = "";

		if (!unpacked)
			unpack();

		str += " " + this.v.name() + ":";
		str += " [" + this.t.name().substring(0, 1) + "]";
		str += " {" + this.m.name() + "}";
		str += " \"" + this.data + "\"";

		return str;
	}
}